package gu.jimgutil;

/**
 * 图像矩阵转换/旋转通用实现<br>
 * 当没有支持当前平台的native library时,调用此类对应的方法,会比native函数慢很多
 * @author guyadong
 *
 */
class CsCvtUniImpl  extends BaseImageCvt{
	static final CsCvtUniImpl INSTANCE = new CsCvtUniImpl();
	private enum MatrixType{RGB,BGR,RGBA}
	private CsCvtUniImpl(){}
	@Override
	void RGBA2BGR(byte[] input,int width,int height,int stride,byte[] output) 
			throws NullPointerException, IllegalArgumentException{
		if(null == input || null == output){
			throw new NullPointerException();
		}
		if (stride < width*4 || input.length != stride * height) {
			throw new IllegalArgumentException("INVALID INPUT SIZE");
		}
		if (output.length != width * height * 3) {
			throw new IllegalArgumentException("INVALID OUTPUT SIZE");
		}
		int input_line=0,output_line=0;
		for (int y = 0; y < height; ++y) {
			for (int x = 0,p=0; x < width*4; x +=4, p+=3) {
				output[output_line+ p       ] = input[input_line + x + 2] ; // BLUE
				output[output_line+ p + 1 ] = input[input_line + x + 1] ; // GREEN
				output[output_line+ p + 2 ] = input[input_line + x      ] ; // RED
			}
			input_line  += stride;
			output_line += width * 3;
		}
	}

	@Override
	void RGBA2RGB(byte[] input,int width,int height,int stride,byte[] output) 
			throws NullPointerException, IllegalArgumentException{
		if(null == input || null == output){
			throw new NullPointerException();
		}
		if (stride < width*4 || input.length != stride * height) {
			throw new IllegalArgumentException("INVALID INPUT SIZE");
		}
		if (output.length != width * height * 3) {
			throw new IllegalArgumentException("INVALID OUTPUT SIZE");
		}
		int input_line=0,output_line=0;
		for (int i = 0; i < height; ++i) {
			for (int x = 0,p=0; x < width*4; x +=4, p+=3) {
				output[output_line+ p       ] = input[input_line + x      ] ; // BLUE
				output[output_line+ p + 1 ] = input[input_line + x + 1] ; // GREEN
				output[output_line+ p + 2 ] = input[input_line + x + 2] ; // RED
			}
			input_line  += stride;
			output_line += width * 3;
		}
	}

	@Override
	void RGBA2GRAY(byte[] input,int width,int height,int stride,byte[] output) 
			throws NullPointerException, IllegalArgumentException{
		if(null == input || null == output){
			throw new NullPointerException();
		}
		if (stride < width*4 || input.length != stride * height) {
			throw new IllegalArgumentException("INVALID INPUT SIZE");
		}
		if (output.length != width * height ) {
			throw new IllegalArgumentException("INVALID OUTPUT SIZE");
		}
		int input_line=0,output_line=0;
		for (int y = 0; y < height; ++y) {
			for (int x = 0,p=0; x < width; ++x, p += 4) {
				int B = input[input_line + p       ] ; // BLUE
				int G = input[input_line + p + 1 ] ; // GREEN
				int R = input[input_line + p + 2 ] ; // RED
				output[output_line + x] = (byte)((R * 76 + G * 150 + B * 30) >> 8);
			}
			input_line  += stride;
			output_line += width;
		}
	}

	@Override
	void RGB2BGR(byte[] input,int width,int height,int stride,byte[] output) 
			throws NullPointerException, IllegalArgumentException{
		if(null == input || null == output){
			throw new NullPointerException();
		}
		if (stride < width*3 || input.length != stride * height) {
			throw new IllegalArgumentException("INVALID INPUT SIZE");
		}
		if (output.length != width * height * 3 ) {
			throw new IllegalArgumentException("INVALID OUTPUT SIZE");
		}
		int input_line=0,output_line=0;
		for (int y = 0; y < height; ++y) {
			for (int x = 0; x < width * 3; x += 3) {
				output[output_line+ x       ] = input[input_line + x + 2 ] ; // BLUE
				output[output_line+ x + 1 ] = input[input_line + x + 1 ] ; // GREEN
				output[output_line+ x + 2 ] = input[input_line + x       ] ; // RED
			}
			input_line += stride;
			output_line += width * 3;
		}
	}
	@Override
	void swap3byte(byte[] input,int width,int height,int stride) 
			throws NullPointerException, IllegalArgumentException{
		if(null == input){
			throw new NullPointerException();
		}
		if (stride < width*3 || input.length != stride * height) {
			throw new IllegalArgumentException("INVALID INPUT SIZE");
		}

		int input_line=0;
		byte swap;
		for (int y = 0; y < height; ++y) {
			for (int x = 0; x < width * 3; x += 3) {
				swap = input[input_line + x + 2 ] ; 
				input[input_line + x + 2 ]  = input[input_line + x       ] ; 
				input[input_line + x       ] = swap;
			}
			input_line += stride;
		}
	}

	@Override
	void RGB2GRAY(byte[] input,int width,int height,int stride,byte[] output) 
			throws NullPointerException, IllegalArgumentException{
		if(null == input || null == output){
			throw new NullPointerException();
		}
		if (stride < width*3 || input.length != stride * height) {
			throw new IllegalArgumentException("INVALID INPUT SIZE");
		}
		if (output.length != width * height ) {
			throw new IllegalArgumentException("INVALID OUTPUT SIZE");
		}
		int input_line=0,output_line=0;
		for (int y = 0; y < height; ++y) {
			for (int x = 0,p=0; x < width; ++x, p += 3) {
				int B = input[input_line + p       ] ; // BLUE
				int G = input[input_line + p + 1 ] ; // GREEN
				int R = input[input_line + p + 2 ] ; // RED
				output[output_line + x] = (byte)((R * 76 + G * 150 + B * 30) >> 8);
			}
			input_line += stride;
			output_line += width;
		}
	}

	@Override
	void BGR2RGBA(byte[] input,int width,int height,int stride,byte[] output) 
			throws NullPointerException, IllegalArgumentException{
		if(null == input || null == output){
			throw new NullPointerException();
		}
		if (stride < width*3 || input.length != stride * height) {
			throw new IllegalArgumentException("INVALID INPUT SIZE");
		}
		if (output.length != width * height * 4) {
			throw new IllegalArgumentException("INVALID OUTPUT SIZE");
		}
		int input_line=0,output_line=0;
		for (int y = 0; y < height; ++y) {
			for (int x = 0,p=0; x < width*3; x +=3, p+=4) {
				output[output_line+ p       ] = input[input_line + x + 2] ; // RED
				output[output_line+ p + 1 ] = input[input_line + x + 1] ; // GREEN
				output[output_line+ p + 2 ] = input[input_line + x      ] ; // BLUE
				output[output_line+ p + 3 ] = 0 ; 
			}
			input_line  += stride;
			output_line += width * 4;
		}
	}

	@Override
	void RGB2RGBA(byte[] input,int width,int height,int stride,byte[] output) 
			throws NullPointerException, IllegalArgumentException{
		if(null == input || null == output){
			throw new NullPointerException();
		}
		if (stride < width*3 || input.length != stride * height) {
			throw new IllegalArgumentException("INVALID INPUT SIZE");
		}
		if (output.length != width * height * 4) {
			throw new IllegalArgumentException("INVALID OUTPUT SIZE");
		}
		int input_line=0,output_line=0;
		for (int i = 0; i < height; ++i) {
			for (int x = 0,p=0; x < width*3; x +=3, p+=4) {
				output[output_line+ p       ] = input[input_line + x      ] ; // RED
				output[output_line+ p + 1 ] = input[input_line + x + 1] ; // GREEN
				output[output_line+ p + 2 ] = input[input_line + x + 2] ; // BLUE
				output[output_line+ p + 3 ] = 0;
			}
			input_line  += stride;
			output_line += width * 4;
		}
	}

	/**
	 * 对数据array中x和y指向的数据交换
	 * @param array 
	 * @param x 
	 * @param y
	 * @param size 数据交换长度
	 * @param tmp 用于数据交换的临时缓冲区，长度必须>= size
	 */
	private static void swap(byte[] array,int x,int y, int size, byte[] tmp){
		System.arraycopy(array, x, tmp, 0, size);
		System.arraycopy(array, y, array, x, size);
		System.arraycopy(tmp, 0, array, y, size);
	}
	private void rotate90(byte[] input,int width,int height,int bpp, int offset) {
		byte[] tmp = new byte[bpp];
		for (int i = 0; i < width; i++) 
		{
			for (int j = 0; j < height; j++) 
			{
				int I = height - 1 - j;
				int J = i;
				while ((i*height + j) > (I*width + J))
				{
					int p = I*width + J;
					int tmp_i = p / height;
					int tmp_j = p % height;
					I = height - 1 - tmp_j;
					J = tmp_i;
				} 
				swap(input,offset + (i*height + j)*bpp, offset +(I*width + J)*bpp, bpp, tmp);
			}
		}
	}
	private void rotate180(byte[] input,int width,int height,int bpp, int offset) {
		byte[] s = new byte[bpp];
		int stride = width*bpp;
		int srcline = 0;
		int dstline = (height -1)*stride;
		for(int y=0;  y < ((height+1)>>1); ++y){
			int dx = stride - bpp;
			for(int sx=0;  sx < stride; sx += bpp, dx -= bpp){
				swap(input, offset + srcline+sx, offset + dstline+dx, bpp, s);
			}
			srcline += stride;
			dstline -= stride;
		}
	}
	private void rotate270(byte[] input,int width,int height,int bpp, int offset) {
		byte[] tmp = new byte[bpp];
		for (int i = 0; i < width; i++) 
		{
			for (int j = 0; j < height; j++) 
			{
				int I = j;
				int J = width - 1 - i;
				while ((i*height + j) > (I*width + J))
				{
					int p = I*width + J;
					int tmp_i = p / height;
					int tmp_j = p % height;
					I = tmp_j;
					J = width - 1 - tmp_i;
				} 
				swap(input,offset + (i*height + j)*bpp, offset +(I*width + J)*bpp, bpp, tmp);
			}
		}
		
	}
	private void hflip(byte[] input,int width,int height,int bpp,int offset) {
		byte[] tmp = new byte[bpp];
		int line = 0;
		int stride = width*bpp;
		for (int i = 0; i < height; i++) 
		{
			for (int j = 0,end_j=(width>>1)*bpp; j < end_j; j +=bpp) 
			{
				swap(input,offset + line + j, offset+ line + (stride-bpp) - j, bpp, tmp);
			}
			line += stride;
		}
	}
	
	private void vflip(byte[] input,int width,int height,int bpp,int offset) {
		int stride = width*bpp;
		byte[] tmp = new byte[stride];	
		for (int srcline = 0,dstline = (height -1)*stride,end_i=(height>>1)*stride; 
				srcline < end_i; 
				srcline+=stride,dstline -= stride) 
		{			
			swap(input,offset + srcline, offset + dstline, stride, tmp);
		}
	}
	
	/**
	 * NV21图像转RGB或BGR
	 * https://blog.csdn.net/sz76211822/article/details/91360986
	 * @param input NV21格式图像数据
	 * @param width 图像宽度
	 * @param height 图像高度
	 * @param output 输出图像缓冲区
	 * @param type 输出图像矩阵类型
	 */
	private void nv21To(byte[]input , int width , int height , byte[]output,MatrixType type)
	{
	    int nvOff = width * height ;
	    int  i, j, yIndex = 0;
	    int y, u, v;
	    int r, g, b, nvIndex = 0;
	    for(i = 0; i < height; i++){
	        for(j = 0; j < width; j ++,++yIndex){
	            nvIndex = (i / 2)  * width + j - j % 2;
	            y = input[yIndex] & 0xff;
	            u = input[nvOff + nvIndex ] & 0xff;
	            v = input[nvOff + nvIndex + 1] & 0xff;

	            // yuv to rgb
	            r = y + ((351 * (v-128))>>8);  //r
	            g = y - ((179 * (v-128) + 86 * (u-128))>>8); //g
	            b = y + ((443 * (u-128))>>8); //b
	            
	            r = ((r>255) ?255 :(r<0)?0:r); 
	            g = ((g>255) ?255 :(g<0)?0:g);
	            b = ((b>255) ?255 :(b<0)?0:b);
	            
	            switch (type) {
				case RGB:
					output[yIndex*3 + 0] = (byte) b;
	            	output[yIndex*3 + 1] = (byte) g;
	            	output[yIndex*3 + 2] = (byte) r;
					break;
				case BGR:
					output[yIndex*3 + 0] = (byte) r;
	            	output[yIndex*3 + 1] = (byte) g;
	            	output[yIndex*3 + 2] = (byte) b;
	            	break;
				case RGBA:
					output[yIndex*4 + 0] = (byte) b;
	            	output[yIndex*4 + 1] = (byte) g;
	            	output[yIndex*4 + 2] = (byte) r;
	            	output[yIndex*4 + 3] = 0;
	            	break;
				default:
					throw new IllegalArgumentException("INVALID destination matrix type " + type);
				}
	        }
	    }
	}
	/**
	 * RGB或BGR转NV21图像
	 * @param input RGB或BGR格式图像数据
	 * @param width 图像宽度
	 * @param height 图像高度
	 * @param output 输出图像缓冲区
	 * @param type 输入图像矩阵类型
	 */
	private void nv21From(byte[]input , int width , int height , byte[]output,MatrixType type)
	{
	    int nvOff = width * height ;
	    int  i, j, yIndex = 0;
	    int y, u, v;
	    int r, g, b, nvIndex = 0;
	    for(i = 0; i < height; i++){
	        for(j = 0; j < width; j ++,++yIndex){
	            nvIndex = i / 2  * width + j - j % 2;
	            switch (type) {
				case RGB:
					b = input[yIndex*3 + 0] & 0xff;
	            	g = input[yIndex*3 + 1] & 0xff;
	            	r = input[yIndex*3 + 2] & 0xff;
					break;
				case BGR:
					r = input[yIndex*3 + 0] & 0xff;
		            g = input[yIndex*3 + 1] & 0xff;
		            b = input[yIndex*3 + 2] & 0xff;
					break;
				case RGBA:
					b = input[yIndex*4 + 0] & 0xff;
	            	g = input[yIndex*4 + 1] & 0xff;
	            	r = input[yIndex*4 + 2] & 0xff;
					break;
				default:
					throw new IllegalArgumentException("INVALID source matrix type " + type);
				}
	            // rgb to yuv
				y = (77*r + 150*g + 29*b)>>8;
				u = ((-44*r  - 87*g  + 131*b)>>8) + 128;
				v = ((131*r - 110*g - 21*b)>>8) + 128 ;
		
	            output[yIndex] = (byte) y;
	            output[nvOff + nvIndex ] = (byte) u ;
	            output[nvOff + nvIndex + 1] = (byte) v;
	        }
	    }
	}
	@Override
	void hflip(byte[] input,int width,int height,int bpp) {
		if(input == null){
			throw new NullPointerException("input is null");
		}
		if (input.length >= width * height * bpp) {
			throw new IllegalArgumentException("INVALID INPUT SIZE");
		}
		if(bpp<1 || bpp >4 ){
			throw new IllegalArgumentException("INVALID bpp,available value:1,2,3,4");
		}
		hflip(input, width, height, bpp, 0);
	}

	@Override
	void vflip(byte[] input,int width,int height,int bpp) {
		if(input == null){
			throw new NullPointerException("input is null");
		}
		if (input.length >= width * height * bpp) {
			throw new IllegalArgumentException("INVALID INPUT SIZE");
		}
		if(bpp<1 || bpp >4 ){
			throw new IllegalArgumentException("INVALID bpp,available value:1,2,3,4");
		}
		vflip(input, width, height, bpp, 0);
	}

	@Override
	void rotate(byte[] input,int width,int height,int bpp,int angle) {
		if(input == null){
			throw new NullPointerException("input is null");
		}
		if (input.length != width * height * bpp) {
			throw new IllegalArgumentException("INVALID INPUT SIZE");
		}
		if(bpp<1 || bpp >4 ){
			throw new IllegalArgumentException("INVALID bpp,available value:1,2,3,4");
		}
		switch(angle){
		case 0:
			return;
		case 90:
			rotate90(input, width, height, bpp, 0);
			return;
		case 180:
			rotate180(input, width, height, bpp, 0);
			return;
		case 270:
			rotate270(input, width, height, bpp, 0);
			return;
		default:
			throw new IllegalArgumentException("INVALID angle,available value:0,90,180,270");				
		}
	}
	@Override
	void NV212RGB(byte[]input , int width , int height , byte[]output){
		if(input == null || output == null){
			throw new NullPointerException("input or output is null");
		}
		if (input.length != (width * height * 3 / 2) ){
			throw new IllegalArgumentException("INVALID INPUT SIZE");
		}
		if (output.length != width * height * 3) {
			throw new IllegalArgumentException("INVALID OUTPUT SIZE");
		}
		nv21To(input, width, height, output, MatrixType.RGB);
	}
	@Override
	void NV212BGR(byte[]input , int width , int height , byte[]output){
		if(input == null || output == null){
			throw new NullPointerException("input or output is null");
		}
		if (input.length != (width * height * 3 / 2) ){
			throw new IllegalArgumentException("INVALID INPUT SIZE");
		}
		if (output.length != width * height * 3) {
			throw new IllegalArgumentException("INVALID OUTPUT SIZE");
		}
		nv21To(input, width, height, output, MatrixType.BGR);
	}
	@Override
	void NV212RGBA(byte[]input , int width , int height , byte[]output){
		if(input == null || output == null){
			throw new NullPointerException("input or output is null");
		}
		if (input.length != (width * height * 3 / 2) ){
			throw new IllegalArgumentException("INVALID INPUT SIZE");
		}
		if (output.length != width * height * 4) {
			throw new IllegalArgumentException("INVALID OUTPUT SIZE");
		}
		nv21To(input, width, height, output, MatrixType.RGBA);
	}
	@Override
	void RGB2NV21(byte[]input, int width, int height, byte[]output) {
		if(input == null || output == null){
			throw new NullPointerException("input or output is null");
		}
		if (input.length != (width * height * 3) ){
			throw new IllegalArgumentException("INVALID INPUT SIZE");
		}
		if (output.length != width * height * 3 /2) {
			throw new IllegalArgumentException("INVALID OUTPUT SIZE");
		}
		if((width & 1) != 0 || (height & 1) != 0){
			throw new IllegalArgumentException("EVEN width and height required");
		}
		nv21From(input, width, height, output, MatrixType.RGB);
	}
	@Override
	void BGR2NV21(byte[]input, int width, int height, byte[]output) {
		if(input == null || output == null){
			throw new NullPointerException("input or output is null");
		}
		if (input.length != (width * height * 3 ) ){
			throw new IllegalArgumentException("INVALID INPUT SIZE");
		}
		if (output.length != width * height * 3 / 2) {
			throw new IllegalArgumentException("INVALID OUTPUT SIZE");
		}
		if((width & 1) != 0 || (height & 1) != 0){
			throw new IllegalArgumentException("EVEN width and height required");
		}
		nv21From(input, width, height, output, MatrixType.BGR);
	}
	void RGBA2NV21(byte[]input, int width, int height, byte[]output) {
		if(input == null || output == null){
			throw new NullPointerException("input or output is null");
		}
		if (input.length != (width * height * 4) ){
			throw new IllegalArgumentException("INVALID INPUT SIZE");
		}
		if (output.length != width * height * 3 /2) {
			throw new IllegalArgumentException("INVALID OUTPUT SIZE");
		}
		if((width & 1) != 0 || (height & 1) != 0){
			throw new IllegalArgumentException("EVEN width and height required");
		}
		nv21From(input, width, height, output, MatrixType.RGBA);
	}
	@Override
	void nv21Rotate(byte[] input,int width,int height,int angle) {
		if(input == null){
			throw new NullPointerException("input is null");
		}
		if (input.length != (width * height * 3 / 2) ){
			throw new IllegalArgumentException("INVALID INPUT SIZE");
		}
		
		switch(angle){
		case 0:
			break;
		case 90:
			// Rotate the Y luma		
			rotate90(input, width, height, 1, 0);		
			// Rotate the U and V color components
			rotate90(input, width/2, height/2, 2, width * height);
			break;
		case 180:
			// Rotate the Y luma		
			rotate180(input, width, height, 1, 0);		
			// Rotate the U and V color components
			rotate180(input, width/2, height/2, 2, width * height);
			break;
		case 270:
			// Rotate the Y luma		
			rotate270(input, width, height, 1, 0);		
			// Rotate the U and V color components
			rotate270(input, width/2, height/2, 2, width * height);
			break;
		default:
			throw new IllegalArgumentException("INVALID angle,available value:0,90,180,270");				
		}
	}
	@Override
	void nv21Hflip(byte[] input,int width,int height) {
		if(input == null){
			throw new NullPointerException("input is null");
		}
		if (input.length != width * height * 3 / 2) {
			throw new IllegalArgumentException("INVALID INPUT SIZE");
		}

		hflip(input, width, height, 1, 0);
		hflip(input, width/2, height/2, 2, width * height );
	}
	@Override
	void nv21Vflip(byte[] input,int width,int height) {
		if(input == null){
			throw new NullPointerException("input is null");
		}
		if (input.length != width * height * 3 / 2 ) {
			throw new IllegalArgumentException("INVALID INPUT SIZE");
		}
		vflip(input, width, height, 1, 0);
		vflip(input, width/2, height/2, 2, width * height );
	}

}
